<template>
  <div class="ju-metro-wrap">
    <div ref="metro"
         class="ju-metro"
         :style="{
          '--box-gutter':Gutter + 'px',
          '--box-width':boxSize[0] + 'px',
          '--box-height':boxSize[1] + 'px'
       }">
      <slot></slot>
    </div>
    <!-- 不给高度就不会触发高度变化时产生的resize -->
    <iframe seamless
            style="top:0;left:0;width:100%;position:absolute;z-index:-1;border:none;box-sizing:border-box;visibility:hidden;opacity:0;pointer-events:none"
            @load="bindIframeResize"></iframe>
  </div>
</template>

<script lang="ts">
  import { Component, NoCache, Prop, Provide, Vue, Watch } from "@some21/vue-class-decorator";
  import { debounce } from "lodash";

  let metroId = 1;
  @Component({
    name: "JuMetro",
  })
  export default class extends Vue {
    $refs: {
      metro: HTMLDivElement;
    };
    metroId = metroId++;
    boxs = [];

    @Provide("metro")
    get metro() {
      return this;
    }

    @Prop({ type: Number, default: 15 })
    Gutter: number;
    @Prop({ type: [Number, Array], default: 200 })
    size: number | number[];
    @Prop({ type: Boolean, default: void 0 })
    round?: boolean;
    @Prop({ type: Boolean, default: false })
    debug: boolean;

    get boxSize() {
      if (this.size) {
        if (typeof this.size === "number") {
          return [this.size, this.size];
        } else if (Array.isArray(this.size)) {
          if (this.size.length > 1) {
            return [Number(this.size[0]), Number(this.size[1])];
          } else if (this.size.length > 0) {
            return [Number(this.size[0]), Number(this.size[0])];
          }
        }
      }
      return [200, 200];
    }

    nameSpace = {};
    basePos = { x: 0, y: 0 }; // 绘制的起始位置
    memory = { flag: Infinity, x: Infinity, y: Infinity };
    // 盒子的基础大小(含边距)
    get gen() {
      return { w: this.boxSize[0] + this.Gutter, h: this.boxSize[1] + this.Gutter };
    }
    // 一行允许的最大数量
    @NoCache
    get cols() {
      if (this.$el) {
        //! 防止可用宽度小于最大格子的宽度而导致的BUG问题
        return Math.max((((this.$el as HTMLDivElement).offsetWidth + this.Gutter) / this.gen.w) >> 0, Math.max(...this.sizeArray.map(m => m[0])));
      } else {
        return 5;
      }
    }
    @NoCache
    get sizeArray() {
      if (this.boxs) {
        return this.boxs.map(m => [Number(m.col), Number(m.row)]);
      }
      return [];
    }

    @Watch("boxs")
    onBoxsChange() {
      // 排序之前先设置一下当前元素允许的最小宽度
      this.sort();
    }

    bindIframeResize(evt) {
      const onWidthChange = debounce(async () => {
        this.sort();
      }, 100);

      (evt.target.contentWindow as Window).addEventListener("resize", onWidthChange);
    }

    updated() {
      this.sort();
    }

    async sort() {
      await Vue.nextTick();
      // 不存在排序内容的时候的处理
      if (!this.sizeArray.length) {
        return;
      }
      const maxCols = Math.max(...this.sizeArray.map(m => m[0]));
      (this.$el as HTMLDivElement).style.minWidth = this.boxSize[0] * maxCols + this.Gutter * (maxCols - 1) + "px";

      // 设置 metro 的实际宽度
      this.$refs.metro.style.width = this.boxSize[0] * this.cols + this.Gutter * (this.cols - 1) + "px";
      this.nameSpace = {};
      this.basePos = { x: 0, y: 0 }; // 绘制的起始位置
      this.memory = { flag: Infinity, x: Infinity, y: Infinity };

      const size = this.sizeArray;

      let maxY = -1;
      let x = this.basePos.x;
      let y = this.basePos.y;
      let memory = this.memory;
      let name: string;
      for (let n = 0; n < size.length; n++) {
        if (memory.flag === 0) {
          x = memory.x;
          y = memory.y;
        }
        memory.flag--;
        if (x > this.cols - 1) {
          // 换行
          x = 0;
          y++;
        }
        name = x + "_" + y; // 对象属性名（反映占领的格子）
        if (name in this.nameSpace) {
          // 判断属性名是否存在
          n--;
          x++;
          memory.flag < Infinity && memory.flag++;
          continue;
        }
        if (size[n][0] * size[n][1] === 1) {
          // 普通格子
          this.nameSpace[name] = [x, y]; // 项值（反映坐标值）
          this.setPos(x, y, n);
          x++;
        } else {
          // 大格子
          if (this.beOver(x, y, size[n])) {
            if (memory.y > y) {
              memory.y = y;
              memory.x = x;
            }
            if (memory.y < Infinity) {
              memory.flag = 1;
            }
            n--;
            x++;
            continue;
          }
          this.nameSpace[name] = [x, y];
          this.setPos(x, y, n);
          this.hold(x, y, size[n]);
          x += size[n][0];
        }
        if (memory.flag === -1) {
          memory = { flag: Infinity, x: Infinity, y: Infinity };
        }
        maxY = Math.max(maxY, y + size[n][1]);
      }
      this.basePos = { x: x, y: y };
      this.memory = memory;
      (this.$refs.metro as HTMLDivElement).style.height = this.gen.h * maxY + "px";
    }
    beOver(x, y, item) {
      // 判断是否会重叠
      let name;
      if (x + item[0] > this.cols) {
        return true;
      } // 超出显示范围
      for (let k = 1; k < item[1]; k++) {
        name = x + "_" + (y - 0 + k);
        if (name in this.nameSpace) {
          return true;
        } // 左侧一列有无重叠
      }
      for (let k = 1; k < item[0]; k++) {
        name = x - 0 + k + "_" + y;
        if (name in this.nameSpace) {
          return true;
        } // 上侧一行有无重叠
      }
      return false;
    }
    hold(x, y, item) {
      // 大格子多占的位置
      for (let t = 0; t < item[0]; t++) {
        for (let k = 0; k < item[1]; k++) {
          const name = x + t + "_" + (y + k);
          if (t === 0 && k === 0) {
            continue;
          }
          this.nameSpace[name] = 0; // 多占的格子无坐标值
        }
      }
    }
    setPos(x, y, n) {
      const left = this.gen.w * x;
      const top = this.gen.h * y;
      const elm = this.boxs[n].$el as HTMLDivElement;
      elm.style.left = left + "px";
      elm.style.top = top + "px";
      elm.style.position = "absolute";
    }
  }
</script>

<style lang="scss" scoped>
  .ju-metro-wrap {
    box-sizing: border-box;
    height: auto;
    max-width: 1060px;
    margin: 0 auto;
  }
  .ju-metro {
    position: relative;
    display: block !important;
    box-sizing: border-box;
    zoom: 1;
    // overflow: hidden; // 不使用比较好，防止内部box 阴影确实
    margin: 0 auto;
    transition: all 500ms ease 0s; // 和子元素的box 保持一直的步调
  }
</style>
